
/*-------------------------------------------------------------------------
 *
 * execPartition.c
 *	  支持分区的例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/executor/execPartition.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/table.h"
#include "access/tableam.h"
#include "catalog/partition.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_type.h"
#include "executor/execPartition.h"
#include "executor/executor.h"
#include "executor/nodeModifyTable.h"
#include "foreign/fdwapi.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "partitioning/partbounds.h"
#include "partitioning/partdesc.h"
#include "partitioning/partprune.h"
#include "rewrite/rewriteManip.h"
#include "utils/acl.h"
#include "utils/lsyscache.h"
#include "utils/partcache.h"
#include "utils/rls.h"
#include "utils/ruleutils.h"


/*-----------------------
 * PartitionTupleRouting - 封装了将插入的元组路由到其叶
 * 分区的所有信息。
 *
 * partition_root
 *		目标命令的分区表。
 *
 * partition_dispatch_info
 *		包含指向每个受元组
 *		路由影响的分区表的 PartitionDispatch 对象的指针的
 *		'mmax_dispatch' 元素数组。 目标分区表的条目
 *		*总是*存在于该数组的第 0 个元素中。 详情请参见
 *		PartitionDispatchData->indexes 的注释。
 *
 * nonleaf_partitions
 *		包含指向非叶分区的假 ResultRelInfo 对象的指针的
 *		'mmax_dispatch' 元素数组，对于检查
 *		分区约束非常有用。
 *
 * num_dispatch
 *		当前存储在 'partition_dispatch_info'
 *		数组中的项数。 也作为下一个空闲数组元素的索引
 *		以存储需要存储的新 PartitionDispatch 对象。
 *
 * max_dispatch
 *		'partition_dispatch_info' 数组的当前分配大小。
 *
 * partitions
 *		包含指向由于元组路由而被触及的每个叶分区的
 *		ResultRelInfo 的指针的 'max_partitions' 元素数组。
 *		其中一些是指向 ResultRelInfos 的指针，是从
 *		拥有的 ModifyTableState 节点借用的。 其余的被专门
 *		构建用于元组路由。 详情请参见
 *		PartitionDispatchData->indexes 的注释。
 *
 * is_borrowed_rel
 *		包含 'max_partitions' 布尔值的数组，用于记录
 *		'partitions' 中的给定条目是否为从拥有的
 *		ModifyTableState 节点借用的 ResultRelInfo 指针，
 *		而不是在这里构建的。
 *
 * num_partitions
 *		当前存储在 'partitions' 数组中的项数。 也
 *		作为下一个空闲数组元素的索引
 *		以存储需要存储的新 ResultRelInfo 对象。
 *
 * max_partitions
 *		'partitions' 数组的当前分配大小。
 *
 * memcxt
 *		用于分配附属结构的内存上下文。
 *-----------------------
 */
struct PartitionTupleRouting
{
	Relation	partition_root;
	PartitionDispatch *partition_dispatch_info;
	ResultRelInfo **nonleaf_partitions;
	int			num_dispatch;
	int			max_dispatch;
	ResultRelInfo **partitions;
	bool	   *is_borrowed_rel;
	int			num_partitions;
	int			max_partitions;
	MemoryContext memcxt;
};

/*-----------------------
 * PartitionDispatch - 一种关于分区层次中一个分区表的信息，
 * 需要将元组路由到其任何分区。 一个 PartitionDispatch
 * 总是封装在一个 PartitionTupleRouting
 * 结构中，并存储在其 'partition_dispatch_info' 数组中。
 *
 * reldesc
 *		表的关系描述符
 *
 * key
 *		表的分区键信息
 *
 * keystate
 *		执行状态，所需分区键中的表达式
 *
 * partdesc
 *		表的分区描述符
 *
 * tupslot
 *		一个独立的 TupleTableSlot，使用该表的元组
 *		描述符初始化，或者如果不需要与父级进行元组转换
 *		则为 NULL。
 *
 * tupmap
 *		TupleConversionMap，用于将父行类型转换为该表的
 *		rowtype（在通过该表路由前提取元组的分区键时）。 
 *      如果不需要元组转换，则存储 NULL 值。
 *
 * indexes
 *		包含 partdesc->nparts 元素的数组。 对于叶分区，
 *		索引对应于封装在 PartitionTupleRouting 的 
 *		partitions 数组中的分区的 ResultRelInfo。 
 *		对于分区分区，索引对应于在其 
 *		partition_dispatch_info 数组中的 PartitionDispatch。
 *		-1 表示我们尚未在 PartitionTupleRouting 为该分区分配任何内容。
 *-----------------------
 */
typedef struct PartitionDispatchData
{
	Relation	reldesc;
	PartitionKey key;
	List	   *keystate;		/* ExprState 列表 */
	PartitionDesc partdesc;
	TupleTableSlot *tupslot;
	AttrMap    *tupmap;
	int			indexes[FLEXIBLE_ARRAY_MEMBER];
}			PartitionDispatchData;


static ResultRelInfo *fc_ExecInitPartitionInfo(ModifyTableState *fc_mtstate,
											EState *fc_estate, PartitionTupleRouting *fc_proute,
											PartitionDispatch fc_dispatch,
											ResultRelInfo *fc_rootResultRelInfo,
											int fc_partidx);
static void fc_ExecInitRoutingInfo(ModifyTableState *fc_mtstate,
								EState *fc_estate,
								PartitionTupleRouting *fc_proute,
								PartitionDispatch fc_dispatch,
								ResultRelInfo *fc_partRelInfo,
								int fc_partidx,
								bool fc_is_borrowed_rel);
static PartitionDispatch fc_ExecInitPartitionDispatchInfo(EState *fc_estate,
													   PartitionTupleRouting *fc_proute,
													   Oid fc_partoid, PartitionDispatch fc_parent_pd,
													   int fc_partidx, ResultRelInfo *fc_rootResultRelInfo);
static void fc_FormPartitionKeyDatum(PartitionDispatch fc_pd,
								  TupleTableSlot *fc_slot,
								  EState *fc_estate,
								  Datum *fc_values,
								  bool *fc_isnull);
static int	get_partition_for_tuple(PartitionDispatch fc_pd, Datum *fc_values,
									bool *fc_isnull);
static char *fc_ExecBuildSlotPartitionKeyDescription(Relation fc_rel,
												  Datum *fc_values,
												  bool *fc_isnull,
												  int fc_maxfieldlen);
static List *fc_adjust_partition_colnos(List *fc_colnos, ResultRelInfo *fc_leaf_part_rri);
static List *fc_adjust_partition_colnos_using_map(List *fc_colnos, AttrMap *fc_attrMap);
static PartitionPruneState *fc_CreatePartitionPruneState(PlanState *fc_planstate,
													  PartitionPruneInfo *fc_pruneinfo);
static void fc_InitPartitionPruneContext(PartitionPruneContext *fc_context,
									  List *fc_pruning_steps,
									  PartitionDesc fc_partdesc,
									  PartitionKey fc_partkey,
									  PlanState *fc_planstate,
									  ExprContext *fc_econtext);
static void fc_PartitionPruneFixSubPlanMap(PartitionPruneState *fc_prunestate,
										Bitmapset *fc_initially_valid_subplans,
										int fc_n_total_subplans);
static void fc_find_matching_subplans_recurse(PartitionPruningData *fc_prunedata,
										   PartitionedRelPruningData *fc_pprune,
										   bool fc_initial_prune,
										   Bitmapset **fc_validsubplans);



/******************************************************************************   
 * ExecSetupPartitionTupleRouting - 设置在分区表中的元组路由所需的信息，封装在   
 * PartitionTupleRouting 中并返回。   
 *   
 * 调用者必须在调用 ExecFindPartition() 时使用返回的 PartitionTupleRouting。   
 * 分区的实际 ResultRelInfo 仅在第一次找到该分区时分配。   
 *   
 * 当前的内存上下文用于分配这个结构体及其后续分配的所有附属结构体。通常   
 * 应该是 estate->es_query_cxt。   
 */  
PartitionTupleRouting *
ExecSetupPartitionTupleRouting(EState *fc_estate, Relation fc_rel)
{
	PartitionTupleRouting *fc_proute;

	/*   
 * 在设置 PartitionTupleRouting 时，我们尽量减少工作量。每个分区的 ResultRelInfo   
 * 按需构建，仅在我们实际需要将元组路由到该分区时才构建。   
 * 这样做的原因是常见情况是 INSERT 将单个元组插入分区表，而这必须快速。   
 */  
	fc_proute = (PartitionTupleRouting *) palloc0(sizeof(PartitionTupleRouting));
	fc_proute->partition_root = fc_rel;
	fc_proute->memcxt = CurrentMemoryContext;
	/* 剩余成员通过归零初始化 */  

	/*   
 * 初始化这个表的 PartitionDispatch 对象。在这里，我们将父级传入 NULL，   
 * 因为我们不需要关心目标分区表的父级。   
 */  
	fc_ExecInitPartitionDispatchInfo(fc_estate, fc_proute, RelationGetRelid(fc_rel),
								  NULL, 0, NULL);

	return fc_proute;
}

/*   
 * ExecFindPartition -- 返回包含在 *slot 中的元组应该属于的叶子分区的 ResultRelInfo。   
 *   
 * 如果分区的 ResultRelInfo 在 'proute' 中尚不存在，则我们设置一个或重用来自   
 * mtstate 的 resultRelInfo 数组中的一个。重用 mtstate 中的 ResultRelInfo 时，我们   
 * 验证该关系是否是 INSERT 的有效目标，并初始化元组路由信息。   
 *   
 * rootResultRelInfo 是查询中命名的关系。   
 *   
 * estate 不能为 NULL；我们需要它来计算分区键中的任何表达式。此外，其每元组上下文   
 * 用作评估临时空间。   
 *   
 * 如果没有找到叶子分区，此例程将以适当的错误信息返回错误。如果找到的目标分区   
 * 不是 INSERT 的有效目标，也可能引发错误。   
 */  
ResultRelInfo * ExecFindPartition(ModifyTableState *fc_mtstate,
				  ResultRelInfo *fc_rootResultRelInfo,
				  PartitionTupleRouting *fc_proute,
				  TupleTableSlot *fc_slot, EState *fc_estate)
{
	PartitionDispatch *fc_pd = fc_proute->partition_dispatch_info;
	Datum		fc_values[PARTITION_MAX_KEYS];
	bool		fc_isnull[PARTITION_MAX_KEYS];
	Relation	fc_rel;
	PartitionDispatch fc_dispatch;
	PartitionDesc fc_partdesc;
	ExprContext *fc_ecxt = GetPerTupleExprContext(fc_estate);
	TupleTableSlot *fc_ecxt_scantuple_saved = fc_ecxt->ecxt_scantuple;
	TupleTableSlot *fc_rootslot = fc_slot;
	TupleTableSlot *fc_myslot = NULL;
	MemoryContext fc_oldcxt;
	ResultRelInfo *fc_rri = NULL;

	/* 在这里使用每个元组上下文以避免内存泄漏 */  
	fc_oldcxt = MemoryContextSwitchTo(GetPerTupleMemoryContext(fc_estate));

	/*   
 * 首先检查根表的分区约束（如果有的话）。如果元组不属于根表本身，   
 * 那么路由这个元组是没有意义的。   
 */  
	if (fc_rootResultRelInfo->ri_RelationDesc->rd_rel->relispartition)
		ExecPartitionCheck(fc_rootResultRelInfo, fc_slot, fc_estate, true);

	/* 从根分区表开始 */  
	fc_dispatch = fc_pd[0];
	while (fc_dispatch != NULL)
	{
		int			fc_partidx = -1;
		bool		fc_is_leaf;

		CHECK_FOR_INTERRUPTS();

		fc_rel = fc_dispatch->reldesc;
		fc_partdesc = fc_dispatch->partdesc;

		/*   
 * 从元组中提取分区键。FormPartitionKeyDatum() 调用的表达式评估机制   
 * 期望 ecxt_scantuple 指向正确的元组插槽。如果当前分区级别的表   
 * 与父级的元组描述符不同，则插槽可能已更改。因此相应更新 ecxt_scantuple。   
 */  
		fc_ecxt->ecxt_scantuple = fc_slot;
		fc_FormPartitionKeyDatum(fc_dispatch, fc_slot, fc_estate, fc_values, fc_isnull);

		/*   
 * 如果这个分区表没有分区或者没有适合这些值的分区，则返回错误。   
 */  
		if (fc_partdesc->nparts == 0 ||
			(fc_partidx = get_partition_for_tuple(fc_dispatch, fc_values, fc_isnull)) < 0)
		{
			char	   *fc_val_desc;

			fc_val_desc = fc_ExecBuildSlotPartitionKeyDescription(fc_rel,
															fc_values, fc_isnull, 64);
			Assert(OidIsValid(RelationGetRelid(fc_rel)));
			ereport(ERROR,
					(errcode(ERRCODE_CHECK_VIOLATION),
					 errmsg("no partition of relation \"%s\" found for row",
							RelationGetRelationName(fc_rel)),
					 fc_val_desc ?
					 errdetail("Partition key of the failing row contains %s.",
							   fc_val_desc) : 0,
					 errtable(fc_rel)));
		}

		fc_is_leaf = fc_partdesc->is_leaf[fc_partidx];
		if (fc_is_leaf)
		{
			/*   
 * 我们已经到达叶子 - 太好了，我们完成了。看看我们是否已经拥有这个分区的   
 * ResultRelInfo。   
 */  
			if (likely(fc_dispatch->indexes[fc_partidx] >= 0))
			{
				/* ResultRelInfo 已经构建 */  
				Assert(fc_dispatch->indexes[fc_partidx] < fc_proute->num_partitions);
				fc_rri = fc_proute->partitions[fc_dispatch->indexes[fc_partidx]];
			}
			else
			{
				/*   
 * 如果该分区在拥有的 ModifyTableState 节点中是已知的，我们可以重用该   
 * ResultRelInfo，而不是通过 ExecInitPartitionInfo() 创建新的。   
 */  
				fc_rri = ExecLookupResultRelByOid(fc_mtstate,
											   fc_partdesc->oids[fc_partidx],
											   true, false);
				if (fc_rri)
				{
					/* 验证这个 ResultRelInfo 是否允许 INSERT */  
					CheckValidResultRel(fc_rri, CMD_INSERT);

					/*   
 * 初始化插入此分区的元组及后续元组所需的信息。   
 */  
					fc_ExecInitRoutingInfo(fc_mtstate, fc_estate, fc_proute, fc_dispatch,
										fc_rri, fc_partidx, true);
				}
				else
				{
					/* 我们需要创建一个新的。 */  
					fc_rri = fc_ExecInitPartitionInfo(fc_mtstate, fc_estate, fc_proute,
												fc_dispatch,
												fc_rootResultRelInfo, fc_partidx);
				}
			}
			Assert(fc_rri != NULL);

			/* 发信号以终止循环 */  
			fc_dispatch = NULL;
		}
		else
		{
			/*   
 * 分区是一个子分区表；获取 PartitionDispatch   
 */  
			if (likely(fc_dispatch->indexes[fc_partidx] >= 0))
			{
				/* 已经构建。 */  
				Assert(fc_dispatch->indexes[fc_partidx] < fc_proute->num_dispatch);

				fc_rri = fc_proute->nonleaf_partitions[fc_dispatch->indexes[fc_partidx]];

				/*   
 * 移动到下一个分区级别并再次搜索，直到找到与此元组匹配的叶子分区   
 */  
				fc_dispatch = fc_pd[fc_dispatch->indexes[fc_partidx]];
			}
			else
			{
				/* 尚未构建。现在构建。 */  
				PartitionDispatch fc_subdispatch;

				/*
				 * 创建新的 PartitionDispatch。我们将当前的作为父 PartitionDispatch 传入。
				 */
				fc_subdispatch = fc_ExecInitPartitionDispatchInfo(fc_estate,
															fc_proute,
															fc_partdesc->oids[fc_partidx],
															fc_dispatch, fc_partidx,
															fc_mtstate->rootResultRelInfo);
				Assert(fc_dispatch->indexes[fc_partidx] >= 0 &&
					   fc_dispatch->indexes[fc_partidx] < fc_proute->num_dispatch);

				fc_rri = fc_proute->nonleaf_partitions[fc_dispatch->indexes[fc_partidx]];
				fc_dispatch = fc_subdispatch;
			}

			/*
			 * 如果新父级与之前的父级不同，则将元组转换为新父级的布局。
			 */
			if (fc_dispatch->tupslot)
			{
				AttrMap    *fc_map = fc_dispatch->tupmap;
				TupleTableSlot *fc_tempslot = fc_myslot;

				fc_myslot = fc_dispatch->tupslot;
				fc_slot = execute_attr_map_slot(fc_map, fc_slot, fc_myslot);

				if (fc_tempslot != NULL)
					ExecClearTuple(fc_tempslot);
			}
		}

		/*
		 * 如果该分区是默认分区，我们必须现在检查其分区约束，这可能由于
		 * 向父级添加分区而同时发生变化。
		 *
		 * （我们在此进行检查，而不依赖 ExecInsert， 
		 * 因为我们不想错过对非叶子分区进行该操作。）
		 */
		if (fc_partidx == fc_partdesc->boundinfo->default_index)
		{
			/*
			 * 元组必须与分区的布局匹配，约束表达式才能成功评估。
			 * 如果分区是子分区，由于上面的代码，这已经是正确的，
			 * 但是对于叶子分区，元组仍然匹配父级的布局。
			 *
			 * 请注意，我们有一个映射可以从根转换为当前分区，但没有
			 * 从直接父级转换为当前分区。
			 * 所以如果我们需要转换，应该从根插槽开始；如果不需要，
			 * 则可以直接使用根插槽。
			 */
			if (fc_is_leaf)
			{
				TupleConversionMap *fc_map = fc_rri->ri_RootToPartitionMap;

				if (fc_map)
					fc_slot = execute_attr_map_slot(fc_map->attrMap, fc_rootslot,
												 fc_rri->ri_PartitionTupleSlot);
				else
					fc_slot = fc_rootslot;
			}

			ExecPartitionCheck(fc_rri, fc_slot, fc_estate, true);
		}
	}

	/* 在最低父级的专用插槽中释放元组。 */
	if (fc_myslot != NULL)
		ExecClearTuple(fc_myslot);
	/* 并恢复 ecxt 的 scantuple */
	fc_ecxt->ecxt_scantuple = fc_ecxt_scantuple_saved;
	MemoryContextSwitchTo(fc_oldcxt);

	return fc_rri;
}

/*
 * ExecInitPartitionInfo
 *		锁定分区并初始化 ResultRelInfo。 还设置分区的其他
 *		信息并将其存储在 proute->partitions 数组中的下一个空插槽中。
 *
 * 返回 ResultRelInfo
 */
static ResultRelInfo *
fc_ExecInitPartitionInfo(ModifyTableState *fc_mtstate, EState *fc_estate,
					  PartitionTupleRouting *fc_proute,
					  PartitionDispatch fc_dispatch,
					  ResultRelInfo *fc_rootResultRelInfo,
					  int fc_partidx)
{
	ModifyTable *fc_node = (ModifyTable *) fc_mtstate->ps.plan;
	Oid			fc_partOid = fc_dispatch->partdesc->oids[fc_partidx];
	Relation	fc_partrel;
	int			fc_firstVarno = fc_mtstate->resultRelInfo[0].ri_RangeTableIndex;
	Relation	fc_firstResultRel = fc_mtstate->resultRelInfo[0].ri_RelationDesc;
	ResultRelInfo *fc_leaf_part_rri;
	MemoryContext fc_oldcxt;
	AttrMap    *fc_part_attmap = NULL;
	bool		fc_found_whole_row;

	fc_oldcxt = MemoryContextSwitchTo(fc_proute->memcxt);

	fc_partrel = table_open(fc_partOid, RowExclusiveLock);

	fc_leaf_part_rri = makeNode(ResultRelInfo);
	InitResultRelInfo(fc_leaf_part_rri,
					  fc_partrel,
					  0,
					  fc_rootResultRelInfo,
					  fc_estate->es_instrument);

	/*
	 * 验证结果关系是否为 INSERT 的有效目标。  对分区键的 UPDATE
	 * 成为 DELETE+INSERT 操作，因此在 CMD_UPDATE 操作时仍
	 * 需要进行此检查。
	 */
	CheckValidResultRel(fc_leaf_part_rri, CMD_INSERT);

	/*
	 * 打开分区索引。用户可能要求在此叶子分区内检查冲突
	 * 并执行“无操作”而不是抛出错误。 为此，请通过初始化 ExecInsert()
	 * 所需的索引信息来做好准备，以执行推测性插入。
	 */
	if (fc_partrel->rd_rel->relhasindex &&
		fc_leaf_part_rri->ri_IndexRelationDescs == NULL)
		ExecOpenIndices(fc_leaf_part_rri,
						(fc_node != NULL &&
						 fc_node->onConflictAction != ONCONFLICT_NONE));

	/*
	 * 为分区构建 WITH CHECK OPTION 约束。 请注意，我们未在规划器内为
	 * 分区构建 withCheckOptionList，但变量编号的简单转换将足够。
	 * 这只在 INSERT 情况下发生，或者在 UPDATE/MERGE 元组路由
	 * 情况下发生，我们没有找到可重用的结果关系。
	 */
	if (fc_node && fc_node->withCheckOptionLists != NIL)
	{
		List	   *fc_wcoList;
		List	   *fc_wcoExprs = NIL;
		ListCell   *fc_ll;

		/*
		 * 在分区表的 INSERT 情况下，只有一个计划。 同样，只有一个 WCO 列表，
		 * 而不是每个分区一个。 对于 UPDATE/MERGE，WCO 列表的数量与计划数量相同。
		 */
		Assert((fc_node->operation == CMD_INSERT &&
				list_length(fc_node->withCheckOptionLists) == 1 &&
				list_length(fc_node->resultRelations) == 1) ||
			   (fc_node->operation == CMD_UPDATE &&
				list_length(fc_node->withCheckOptionLists) ==
				list_length(fc_node->resultRelations)) ||
			   (fc_node->operation == CMD_MERGE &&
				list_length(fc_node->withCheckOptionLists) ==
				list_length(fc_node->resultRelations)));

		/*
		 * 使用第一个计划的 WCO 列表作为参考来计算该分区的 WCO 列表的 attno。
		 * 在 INSERT 情况下，这指的是根分区表，而在 UPDATE 元组路由
		 * 情况下，这指的是 mtstate->resultRelInfo 数组中的第一个分区。
		 * 在任何情况下，这两个关系和此分区应具有相同的列，
		 * 因此我们应该能够成功地映射属性。
		 */
		fc_wcoList = linitial(fc_node->withCheckOptionLists);

		/*
		 * 将其中的 Vars 转换为包含该分区的属性编号。
		 */
		fc_part_attmap =
			build_attrmap_by_name(RelationGetDescr(fc_partrel),
								  RelationGetDescr(fc_firstResultRel));
		fc_wcoList = (List *)
			map_variable_attnos((Node *) fc_wcoList,
								fc_firstVarno, 0,
								fc_part_attmap,
								RelationGetForm(fc_partrel)->reltype,
								&fc_found_whole_row);
		/* 我们忽略 found_whole_row 的值。 */

		foreach(fc_ll, fc_wcoList)
		{
			WithCheckOption *fc_wco = lfirst_node(WithCheckOption, fc_ll);
			ExprState  *fc_wcoExpr = ExecInitQual(castNode(List, fc_wco->qual),
											   &fc_mtstate->ps);

			fc_wcoExprs = lappend(fc_wcoExprs, fc_wcoExpr);
		}

		fc_leaf_part_rri->ri_WithCheckOptions = fc_wcoList;
		fc_leaf_part_rri->ri_WithCheckOptionExprs = fc_wcoExprs;
	}

	/*
	 * 为分区构建 RETURNING 投影。 请注意，我们未在规划器内为分区构建
	 * returningList，但变量编号的简单转换将足够。
	 * 这只在 INSERT 情况下发生，或者在 UPDATE 元组路由
	 * 情况下发生，我们没有找到可重用的结果关系。
	 */
	if (fc_node && fc_node->returningLists != NIL)
	{
		TupleTableSlot *fc_slot;
		ExprContext *fc_econtext;
		List	   *fc_returningList;

		/* 有关 WCO 列表的详细信息，请参阅上面的评论。 */
		/* （除了 MERGE 目前不支持 RETURNING） */
		Assert((fc_node->operation == CMD_INSERT &&
				list_length(fc_node->returningLists) == 1 &&
				list_length(fc_node->resultRelations) == 1) ||
			   (fc_node->operation == CMD_UPDATE &&
				list_length(fc_node->returningLists) ==
				list_length(fc_node->resultRelations)));

		/*
		 * 使用第一个计划的 RETURNING 列表作为参考来计算该分区的
		 * RETURNING 列表的 attno。 有关为什么这样做是可以的更多细节，
		 * 请参阅上面的 WCO 列表的评论。
		 */
		fc_returningList = linitial(fc_node->returningLists);

		/*
		 * 将其中的 Vars 转换为包含该分区的属性编号。
		 */
		if (fc_part_attmap == NULL)
			fc_part_attmap =
				build_attrmap_by_name(RelationGetDescr(fc_partrel),
									  RelationGetDescr(fc_firstResultRel));
		fc_returningList = (List *)
			map_variable_attnos((Node *) fc_returningList,
								fc_firstVarno, 0,
								fc_part_attmap,
								RelationGetForm(fc_partrel)->reltype,
								&fc_found_whole_row);
		/* 我们忽略 found_whole_row 的值。 */

		fc_leaf_part_rri->ri_returningList = fc_returningList;

		/*
		 * 初始化投影本身。
		 *
		 * 使用将在 ExecInitModifyTable() 中为投影输出设置的插槽和表达式上下文。
		 */
		Assert(fc_mtstate->ps.ps_ResultTupleSlot != NULL);
		fc_slot = fc_mtstate->ps.ps_ResultTupleSlot;
		Assert(fc_mtstate->ps.ps_ExprContext != NULL);
		fc_econtext = fc_mtstate->ps.ps_ExprContext;
		fc_leaf_part_rri->ri_projectReturning =
			ExecBuildProjectionInfo(fc_returningList, fc_econtext, fc_slot,
									&fc_mtstate->ps, RelationGetDescr(fc_partrel));
	}

	/* 设置将元组路由到分区所需的信息。 */
	fc_ExecInitRoutingInfo(fc_mtstate, fc_estate, fc_proute, fc_dispatch,
						fc_leaf_part_rri, fc_partidx, false);

	/*
	 * 如果有 ON CONFLICT 子句，请初始化其状态。
	 */
	if (fc_node && fc_node->onConflictAction != ONCONFLICT_NONE)
	{
		TupleDesc	fc_partrelDesc = RelationGetDescr(fc_partrel);
		ExprContext *fc_econtext = fc_mtstate->ps.ps_ExprContext;
		ListCell   *fc_lc;
		List	   *fc_arbiterIndexes = NIL;

		/*
		 * 如果有一个仲裁索引列表，将其映射到分区中的索引列表。
		 * 我们通过扫描分区的索引列表并搜索祖先表中每个索引的
		 * 祖先关系来做到这一点。
		 */
		if (list_length(fc_rootResultRelInfo->ri_onConflictArbiterIndexes) > 0)
		{
			List	   *fc_childIdxs;

			fc_childIdxs = RelationGetIndexList(fc_leaf_part_rri->ri_RelationDesc);

			foreach(fc_lc, fc_childIdxs)
			{
				Oid			fc_childIdx = lfirst_oid(fc_lc);
				List	   *fc_ancestors;
				ListCell   *fc_lc2;

				fc_ancestors = get_partition_ancestors(fc_childIdx);
				foreach(fc_lc2, fc_rootResultRelInfo->ri_onConflictArbiterIndexes)
				{
					if (list_member_oid(fc_ancestors, lfirst_oid(fc_lc2)))
						fc_arbiterIndexes = lappend_oid(fc_arbiterIndexes, fc_childIdx);
				}
				list_free(fc_ancestors);
			}
		}

		/*
		 * 如果结果列表的长度不相等，那就出问题了。
		 * （这不应该发生，因为仲裁索引选择不应该选到无效索引。）
		 */
		if (list_length(fc_rootResultRelInfo->ri_onConflictArbiterIndexes) !=
			list_length(fc_arbiterIndexes))
			elog(ERROR, "invalid arbiter index list");
		fc_leaf_part_rri->ri_onConflictArbiterIndexes = fc_arbiterIndexes;

		/*
		 * 在 DO UPDATE 的情况下，我们还有一些状态需要初始化。
		 */
		if (fc_node->onConflictAction == ONCONFLICT_UPDATE)
		{
			OnConflictSetState *fc_onconfl = makeNode(OnConflictSetState);
			TupleConversionMap *fc_map;

			fc_map = fc_leaf_part_rri->ri_RootToPartitionMap;

			Assert(fc_node->onConflictSet != NIL);
			Assert(fc_rootResultRelInfo->ri_onConflict != NULL);

			fc_leaf_part_rri->ri_onConflict = fc_onconfl;

			/*
			 * 每个分区需要一个单独的现有插槽，因为即使元组
			 * 描述符匹配，分区也可能属于不同的 AM。
			 */
			fc_onconfl->oc_Existing =
				table_slot_create(fc_leaf_part_rri->ri_RelationDesc,
								  &fc_mtstate->ps.state->es_tupleTable);

			
/*
			 * 如果分区的元组描述符与根父级完全匹配（这是常见情况），我们可以重用大部分父级的 ON
			 * CONFLICT SET 状态，从而跳过大量工作。否则，我们需要为此分区创建特定的状态。
			 */
			if (fc_map == NULL)
			{
				/*
				 * 从分区根重用这些是安全的，因为我们一次只处理一个元组（因此不会
				 * 覆盖槽中所需的数据），并且投影的结果与基础存储无关。
				 * 投影和 WHERE 子句本身不存储状态
				 * 与基础存储无关。
				 */
				fc_onconfl->oc_ProjSlot =
					fc_rootResultRelInfo->ri_onConflict->oc_ProjSlot;
				fc_onconfl->oc_ProjInfo =
					fc_rootResultRelInfo->ri_onConflict->oc_ProjInfo;
				fc_onconfl->oc_WhereClause =
					fc_rootResultRelInfo->ri_onConflict->oc_WhereClause;
			}
			else
			{
				List	   *fc_onconflset;
				List	   *fc_onconflcols;
				bool		fc_found_whole_row;

				/*
				 * 翻译 onConflictSet 中的表达式以考虑
				 * 不同的属性编号。为此，将分区
				 * varattnos 映射两次：第一次捕获 EXCLUDED
				 * 伪关系（INNER_VAR），第二次处理主
				 * 目标关系（firstVarno）。
				 */
				fc_onconflset = copyObject(fc_node->onConflictSet);
				if (fc_part_attmap == NULL)
					fc_part_attmap =
						build_attrmap_by_name(RelationGetDescr(fc_partrel),
											  RelationGetDescr(fc_firstResultRel));
				fc_onconflset = (List *)
					map_variable_attnos((Node *) fc_onconflset,
										INNER_VAR, 0,
										fc_part_attmap,
										RelationGetForm(fc_partrel)->reltype,
										&fc_found_whole_row);
				/* 我们忽略 found_whole_row 的值。 */
				fc_onconflset = (List *)
					map_variable_attnos((Node *) fc_onconflset,
										fc_firstVarno, 0,
										fc_part_attmap,
										RelationGetForm(fc_partrel)->reltype,
										&fc_found_whole_row);
				/* 我们忽略 found_whole_row 的值。 */

				/* 最后，调整目标 colnos 以匹配分区。 */
				fc_onconflcols = fc_adjust_partition_colnos(fc_node->onConflictCols,
													  fc_leaf_part_rri);

				/* 为 UPDATE SET 投影创建元组槽 */
				fc_onconfl->oc_ProjSlot =
					table_slot_create(fc_partrel,
									  &fc_mtstate->ps.state->es_tupleTable);

				/* 构建 UPDATE SET 投影状态 */
				fc_onconfl->oc_ProjInfo =
					ExecBuildUpdateProjection(fc_onconflset,
											  true,
											  fc_onconflcols,
											  fc_partrelDesc,
											  fc_econtext,
											  fc_onconfl->oc_ProjSlot,
											  &fc_mtstate->ps);

				/*
				 * 如果存在 WHERE 子句，初始化状态以便进行评估，并适当地映射属性编号。
				 * 与 onConflictSet 一样，我们需要将分区 varattnos 映射到分区的 tupdesc。
				 */
				if (fc_node->onConflictWhere)
				{
					List	   *fc_clause;

					fc_clause = copyObject((List *) fc_node->onConflictWhere);
					fc_clause = (List *)
						map_variable_attnos((Node *) fc_clause,
											INNER_VAR, 0,
											fc_part_attmap,
											RelationGetForm(fc_partrel)->reltype,
											&fc_found_whole_row);
					/* 我们忽略 found_whole_row 的值。 */
					fc_clause = (List *)
						map_variable_attnos((Node *) fc_clause,
											fc_firstVarno, 0,
											fc_part_attmap,
											RelationGetForm(fc_partrel)->reltype,
											&fc_found_whole_row);
					/* 我们忽略 found_whole_row 的值。 */
					fc_onconfl->oc_WhereClause =
						ExecInitQual((List *) fc_clause, &fc_mtstate->ps);
				}
			}
		}
	}

	/*
	 * 由于我们刚刚初始化了这个 ResultRelInfo，它尚未在任何与 estate 附加的列表中。
	 * 将其添加，以便以后可以找到它。
	 *
	 * 请注意，这个列表中的条目没有任何预定的顺序，
	 * 因为分区结果关系是在需要时初始化的。
	 */
	MemoryContextSwitchTo(fc_estate->es_query_cxt);
	fc_estate->es_tuple_routing_result_relations =
		lappend(fc_estate->es_tuple_routing_result_relations,
				fc_leaf_part_rri);

	/*
	 * 初始化处理 MERGE 所需的此分区的信息。
	 * 我们以“第一个”结果关系的 mergeActionList 作为参考并为这个关系复制，
	 * 将引用属性编号的内容转换为匹配此关系的格式。
	 *
	 * 这重复了 ExecInitMerge() 中的大部分逻辑，因此如果那里有变化，也请查看这里。
	 */
	if (fc_node && fc_node->operation == CMD_MERGE)
	{
		List	   *fc_firstMergeActionList = linitial(fc_node->mergeActionLists);
		ListCell   *fc_lc;
		ExprContext *fc_econtext = fc_mtstate->ps.ps_ExprContext;

		if (fc_part_attmap == NULL)
			fc_part_attmap =
				build_attrmap_by_name(RelationGetDescr(fc_partrel),
									  RelationGetDescr(fc_firstResultRel));

		if (unlikely(!fc_leaf_part_rri->ri_projectNewInfoValid))
			ExecInitMergeTupleSlots(fc_mtstate, fc_leaf_part_rri);

		foreach(fc_lc, fc_firstMergeActionList)
		{
			/* 为该关系安全起见进行复制。  */
			MergeAction *fc_action = copyObject(lfirst(fc_lc));
			MergeActionState *fc_action_state;
			List	  **fc_list;

			/* 为该关系生成操作的状态 */
			fc_action_state = makeNode(MergeActionState);
			fc_action_state->mas_action = fc_action;

			/* 并将操作放入适当的列表中 */
			if (fc_action->matched)
				fc_list = &fc_leaf_part_rri->ri_matchedMergeAction;
			else
				fc_list = &fc_leaf_part_rri->ri_notMatchedMergeAction;
			*fc_list = lappend(*fc_list, fc_action_state);

			switch (fc_action->commandType)
			{
				case CMD_INSERT:

					/*
					 * 在初始化“第一个”结果关系时，已对目标列表执行 ExecCheckPlanOutput()，并且
					 * 对所有结果关系都是相同的。
					 */
					fc_action_state->mas_proj =
						ExecBuildProjectionInfo(fc_action->targetList, fc_econtext,
												fc_leaf_part_rri->ri_newTupleSlot,
												&fc_mtstate->ps,
												RelationGetDescr(fc_partrel));
					break;
				case CMD_UPDATE:

					/*
					 * 将“第一个”结果关系的 updateColnos 属性编号转换为该结果关系的。
					 */
					if (fc_part_attmap)
						fc_action->updateColnos =
							fc_adjust_partition_colnos_using_map(fc_action->updateColnos,
															  fc_part_attmap);
					fc_action_state->mas_proj =
						ExecBuildUpdateProjection(fc_action->targetList,
												  true,
												  fc_action->updateColnos,
												  RelationGetDescr(fc_leaf_part_rri->ri_RelationDesc),
												  fc_econtext,
												  fc_leaf_part_rri->ri_newTupleSlot,
												  NULL);
					break;
				case CMD_DELETE:
					break;

				default:
					elog(ERROR, "unknown action in MERGE WHEN clause");
			}

			/* found_whole_row 有意被忽略。 */
			fc_action->qual =
				map_variable_attnos(fc_action->qual,
									fc_firstVarno, 0,
									fc_part_attmap,
									RelationGetForm(fc_partrel)->reltype,
									&fc_found_whole_row);
			fc_action_state->mas_whenqual =
				ExecInitQual((List *) fc_action->qual, &fc_mtstate->ps);
		}
	}
	MemoryContextSwitchTo(fc_oldcxt);

	return fc_leaf_part_rri;
}

/*
 * ExecInitRoutingInfo
 *		设置在根分区表格式和分区格式之间转换元组所需的信息，并在 PartitionTupleRouting 中跟踪它。
 */
static void fc_ExecInitRoutingInfo(ModifyTableState *fc_mtstate,
					EState *fc_estate,
					PartitionTupleRouting *fc_proute,
					PartitionDispatch fc_dispatch,
					ResultRelInfo *fc_partRelInfo,
					int fc_partidx,
					bool fc_is_borrowed_rel)
{
	ResultRelInfo *fc_rootRelInfo = fc_partRelInfo->ri_RootResultRelInfo;
	MemoryContext fc_oldcxt;
	int			fc_rri_index;

	fc_oldcxt = MemoryContextSwitchTo(fc_proute->memcxt);

	/*
	 * 设置一个元组转换映射，将从父类型路由到分区的元组转换为分区的类型。
	 */
	fc_partRelInfo->ri_RootToPartitionMap =
		convert_tuples_by_name(RelationGetDescr(fc_rootRelInfo->ri_RelationDesc),
							   RelationGetDescr(fc_partRelInfo->ri_RelationDesc));

	/*
	 * 如果分区的行类型与根父级不同，则初始化一个专门用于存储该分区元组的插槽。
	 * 该插槽用于在路由后应用于元组的各种操作，例如检查约束。
	 */
	if (fc_partRelInfo->ri_RootToPartitionMap != NULL)
	{
		Relation	fc_partrel = fc_partRelInfo->ri_RelationDesc;

		/*
		 * 初始化插槽本身，将其描述符设置为该分区的 TupleDesc；
		 * TupleDesc 引用将在命令结束时释放。
		 */
		fc_partRelInfo->ri_PartitionTupleSlot =
			table_slot_create(fc_partrel, &fc_estate->es_tupleTable);
	}
	else
		fc_partRelInfo->ri_PartitionTupleSlot = NULL;

	/*
	 * 如果分区是外部表，让 FDW 为路由元组到分区自我初始化。
	 */
	if (fc_partRelInfo->ri_FdwRoutine != NULL &&
		fc_partRelInfo->ri_FdwRoutine->BeginForeignInsert != NULL)
		fc_partRelInfo->ri_FdwRoutine->BeginForeignInsert(fc_mtstate, fc_partRelInfo);

	/*
	 * 确定 FDW 是否支持批量插入并确定批量大小
	 * （一个 FDW 可能支持批处理，但可能在服务器/表或特定查询中禁用）。
	 *
	 * 如果 FDW 不支持批处理，我们将批量大小设置为1。
	 */
	if (fc_mtstate->operation == CMD_INSERT &&
		fc_partRelInfo->ri_FdwRoutine != NULL &&
		fc_partRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize &&
		fc_partRelInfo->ri_FdwRoutine->ExecForeignBatchInsert)
		fc_partRelInfo->ri_BatchSize =
			fc_partRelInfo->ri_FdwRoutine->GetForeignModifyBatchSize(fc_partRelInfo);
	else
		fc_partRelInfo->ri_BatchSize = 1;

	Assert(fc_partRelInfo->ri_BatchSize >= 1);

	fc_partRelInfo->ri_CopyMultiInsertBuffer = NULL;

	/*
	 * 在 PartitionTupleRouting->partitions 数组中跟踪此信息。
	 */
	Assert(fc_dispatch->indexes[fc_partidx] == -1);

	fc_rri_index = fc_proute->num_partitions++;

	/* 根据需要分配或扩大数组 */
	if (fc_proute->num_partitions >= fc_proute->max_partitions)
	{
		if (fc_proute->max_partitions == 0)
		{
			fc_proute->max_partitions = 8;
			fc_proute->partitions = (ResultRelInfo **)
				palloc(sizeof(ResultRelInfo *) * fc_proute->max_partitions);
			fc_proute->is_borrowed_rel = (bool *)
				palloc(sizeof(bool) * fc_proute->max_partitions);
		}
		else
		{
			fc_proute->max_partitions *= 2;
			fc_proute->partitions = (ResultRelInfo **)
				repalloc(fc_proute->partitions, sizeof(ResultRelInfo *) *
						 fc_proute->max_partitions);
			fc_proute->is_borrowed_rel = (bool *)
				repalloc(fc_proute->is_borrowed_rel, sizeof(bool) *
						 fc_proute->max_partitions);
		}
	}

	fc_proute->partitions[fc_rri_index] = fc_partRelInfo;
	fc_proute->is_borrowed_rel[fc_rri_index] = fc_is_borrowed_rel;
	fc_dispatch->indexes[fc_partidx] = fc_rri_index;

	MemoryContextSwitchTo(fc_oldcxt);
}

/*
 * ExecInitPartitionDispatchInfo
 *		锁定分区表（如果尚未锁定）并初始化分区表的 PartitionDispatch，并将其存储在
 *		proute->partition_dispatch_info 数组中的下一个可用插槽中。
 *		此外，在 parent_pd->indexes[] 数组的 partidx 元素中记录对该数组的索引，
 *		以便我们可以为后续正确检索新创建的 PartitionDispatch。
 */
static PartitionDispatch
fc_ExecInitPartitionDispatchInfo(EState *fc_estate,
							  PartitionTupleRouting *fc_proute, Oid fc_partoid,
							  PartitionDispatch fc_parent_pd, int fc_partidx,
							  ResultRelInfo *fc_rootResultRelInfo)
{
	Relation	fc_rel;
	PartitionDesc fc_partdesc;
	PartitionDispatch fc_pd;
	int			fc_dispatchidx;
	MemoryContext fc_oldcxt;

	/*
	 * 对于数据修改，执行器最好不要包括被分离的分区，除非在快照隔离模式下运行。
	 * 这意味着，当一个元组插入到一个正在并发分离的分区时，
	 * 读取已提交的事务立即会收到“没有元组分区”的错误，但可重复读取模式下的事务仍然可以使用这样的分区。
	 */
	if (fc_estate->es_partition_directory == NULL)
		fc_estate->es_partition_directory =
			CreatePartitionDirectory(fc_estate->es_query_cxt,
									 !IsolationUsesXactSnapshot());

	fc_oldcxt = MemoryContextSwitchTo(fc_proute->memcxt);

	/*
	 * 这里只需要锁定子分区表。
	 * 根分区表将在查询的 rtable 中引用，因此将已经被锁定。
	 */
	if (fc_partoid != RelationGetRelid(fc_proute->partition_root))
		fc_rel = table_open(fc_partoid, RowExclusiveLock);
	else
		fc_rel = fc_proute->partition_root;
	fc_partdesc = PartitionDirectoryLookup(fc_estate->es_partition_directory, fc_rel);

	fc_pd = (PartitionDispatch) palloc(offsetof(PartitionDispatchData, indexes) +
									fc_partdesc->nparts * sizeof(int));
	fc_pd->reldesc = fc_rel;
	fc_pd->key = RelationGetPartitionKey(fc_rel);
	fc_pd->keystate = NIL;
	fc_pd->partdesc = fc_partdesc;
	if (fc_parent_pd != NULL)
	{
		TupleDesc	fc_tupdesc = RelationGetDescr(fc_rel);

		/*
		 * 对于列顺序与其直接父分区表不同的子分区表，
		 * 我们必须存储一个元组表插槽，初始化其元组描述符以及一个元组转换映射，
		 * 将元组从其父行类型转换为它自己的。
		 * 这可以确保我们在计算其分区键进行元组路由时，使用正确的元组描述符查看正确的行。
		 */
		fc_pd->tupmap = build_attrmap_by_name_if_req(RelationGetDescr(fc_parent_pd->reldesc),
												  fc_tupdesc);
		fc_pd->tupslot = fc_pd->tupmap ?
			MakeSingleTupleTableSlot(fc_tupdesc, &TTSOpsVirtual) : NULL;
	}
	else
	{
		/* 不需要根分区表 */
		fc_pd->tupmap = NULL;
		fc_pd->tupslot = NULL;
	}

	/*
	 * 初始化为 -1，以表明对应分区的 ResultRelInfo 或 PartitionDispatch 尚未创建。
	 */
	memset(fc_pd->indexes, -1, sizeof(int) * fc_partdesc->nparts);

	/* 在 PartitionTupleRouting 中跟踪以供后用 */
	fc_dispatchidx = fc_proute->num_dispatch++;

	/* 根据需要分配或扩大数组 */
	if (fc_proute->num_dispatch >= fc_proute->max_dispatch)
	{
		if (fc_proute->max_dispatch == 0)
		{
			fc_proute->max_dispatch = 4;
			fc_proute->partition_dispatch_info = (PartitionDispatch *)
				palloc(sizeof(PartitionDispatch) * fc_proute->max_dispatch);
			fc_proute->nonleaf_partitions = (ResultRelInfo **)
				palloc(sizeof(ResultRelInfo *) * fc_proute->max_dispatch);
		}
		else
		{
			fc_proute->max_dispatch *= 2;
			fc_proute->partition_dispatch_info = (PartitionDispatch *)
				repalloc(fc_proute->partition_dispatch_info,
						 sizeof(PartitionDispatch) * fc_proute->max_dispatch);
			fc_proute->nonleaf_partitions = (ResultRelInfo **)
				repalloc(fc_proute->nonleaf_partitions,
						 sizeof(ResultRelInfo *) * fc_proute->max_dispatch);
		}
	}
	fc_proute->partition_dispatch_info[fc_dispatchidx] = fc_pd;

	/*
	 * 如果为子分区表设置 PartitionDispatch，可能还需要一个最小有效的 ResultRelInfo，以便后续检查分区约束；
	 * 现在设置它。
	 */
	if (fc_parent_pd)
	{
		ResultRelInfo *fc_rri = makeNode(ResultRelInfo);

		InitResultRelInfo(fc_rri, fc_rel, 0, fc_rootResultRelInfo, 0);
		fc_proute->nonleaf_partitions[fc_dispatchidx] = fc_rri;
	}
	else
		fc_proute->nonleaf_partitions[fc_dispatchidx] = NULL;

	
/*
	 * 最后，如果为子分区表设置 PartitionDispatch，
	 * 在父级中安装一个下行链路以允许快速下降。
	 */
	if (fc_parent_pd)
	{
		Assert(fc_parent_pd->indexes[fc_partidx] == -1);
		fc_parent_pd->indexes[fc_partidx] = fc_dispatchidx;
	}

	MemoryContextSwitchTo(fc_oldcxt);

	return fc_pd;
}

/*
 * ExecCleanupTupleRouting -- 清理为分区元组路由分配的对象。
 *
 * 关闭所有分区表、叶分区及其索引。
 */
void ExecCleanupTupleRouting(ModifyTableState *fc_mtstate,
						PartitionTupleRouting *fc_proute)
{
	int			fc_i;

	/*
	 * 请记住，proute->partition_dispatch_info[0] 对应于根
	 * 分区表，我们必须不尝试关闭它，因为它是查询的
	 * 主要目标表，这个表将由 ExecEndPlan() 或
	 * DoCopy() 等调用者关闭。此外，根
	 * 分区表的 tupslot 为 NULL。
	 */
	for (fc_i = 1; fc_i < fc_proute->num_dispatch; fc_i++)
	{
		PartitionDispatch fc_pd = fc_proute->partition_dispatch_info[fc_i];

		table_close(fc_pd->reldesc, NoLock);

		if (fc_pd->tupslot)
			ExecDropSingleTupleTableSlot(fc_pd->tupslot);
	}

	for (fc_i = 0; fc_i < fc_proute->num_partitions; fc_i++)
	{
		ResultRelInfo *fc_resultRelInfo = fc_proute->partitions[fc_i];

		/* 允许任何 FDWs 关闭 */
		if (fc_resultRelInfo->ri_FdwRoutine != NULL &&
			fc_resultRelInfo->ri_FdwRoutine->EndForeignInsert != NULL)
			fc_resultRelInfo->ri_FdwRoutine->EndForeignInsert(fc_mtstate->ps.state,
														   fc_resultRelInfo);

		/*
		 * 如果它不是从拥有 ModifyTableState 借用的结果关系之一，
		 * 则关闭它；那些将由 ExecEndPlan() 关闭。
		 */
		if (fc_proute->is_borrowed_rel[fc_i])
			continue;

		ExecCloseIndices(fc_resultRelInfo);
		table_close(fc_resultRelInfo->ri_RelationDesc, NoLock);
	}
}

/***********************
 *		FormPartitionKeyDatum
 *			构造元组的分区键的 values[] 和 isnull[] 数组。
 *
 *	pd				分区表的分区调度对象
 *	slot			从中提取分区键的 Heap 元组
 *	estate			用于评估任何分区键表达式的执行状态
 *					(必须为非 NULL)
 *	values			分区键 Datums 数组（输出区域）
 *	isnull			是否为空指示器数组（输出区域）
 *
 * estate 的 per-tuple 表达式上下文的 ecxt_scantuple 插槽必须指向
 * 传入的 Heap 元组。
 ***********************/
static void fc_FormPartitionKeyDatum(PartitionDispatch fc_pd,
					  TupleTableSlot *fc_slot,
					  EState *fc_estate,
					  Datum *fc_values,
					  bool *fc_isnull)
{
	ListCell   *fc_partexpr_item;
	int			fc_i;

	if (fc_pd->key->partexprs != NIL && fc_pd->keystate == NIL)
	{
		/* 检查调用者是否已正确设置上下文 */
		Assert(fc_estate != NULL &&
			   GetPerTupleExprContext(fc_estate)->ecxt_scantuple == fc_slot);

		/* 第一次通过时，设置表达式评估状态 */
		fc_pd->keystate = ExecPrepareExprList(fc_pd->key->partexprs, fc_estate);
	}

	fc_partexpr_item = list_head(fc_pd->keystate);
	for (fc_i = 0; fc_i < fc_pd->key->partnatts; fc_i++)
	{
		AttrNumber	fc_keycol = fc_pd->key->partattrs[fc_i];
		Datum		fc_datum;
		bool		fc_isNull;

		if (fc_keycol != 0)
		{
			/* 普通列；直接从 Heap 元组中获取值 */
			fc_datum = slot_getattr(fc_slot, fc_keycol, &fc_isNull);
		}
		else
		{
			/* 表达式；需要进行评估 */
			if (fc_partexpr_item == NULL)
				elog(ERROR, "wrong number of partition key expressions");
			fc_datum = ExecEvalExprSwitchContext((ExprState *) lfirst(fc_partexpr_item),
											  GetPerTupleExprContext(fc_estate),
											  &fc_isNull);
			fc_partexpr_item = lnext(fc_pd->keystate, fc_partexpr_item);
		}
		fc_values[fc_i] = fc_datum;
		fc_isnull[fc_i] = fc_isNull;
	}

	if (fc_partexpr_item != NULL)
		elog(ERROR, "wrong number of partition key expressions");
}

/*
 * get_partition_for_tuple
 *		查找接受指定在 values 和 isnull 中的分区键的关系分区
 *
 * 返回值是找到的分区的索引（>= 0 并且 < partdesc->nparts），如果未找到则为 -1。
 */
static int get_partition_for_tuple(PartitionDispatch fc_pd, Datum *fc_values, bool *fc_isnull)
{
	int			fc_bound_offset;
	int			fc_part_index = -1;
	PartitionKey fc_key = fc_pd->key;
	PartitionDesc fc_partdesc = fc_pd->partdesc;
	PartitionBoundInfo fc_boundinfo = fc_partdesc->boundinfo;

	/* 根据分区策略适当路由。 */
	switch (fc_key->strategy)
	{
		case PARTITION_STRATEGY_HASH:
			{
				uint64		fc_rowHash;

				fc_rowHash = compute_partition_hash_value(fc_key->partnatts,
													   fc_key->partsupfunc,
													   fc_key->partcollation,
													   fc_values, fc_isnull);

				fc_part_index = fc_boundinfo->indexes[fc_rowHash % fc_boundinfo->nindexes];
			}
			break;

		case PARTITION_STRATEGY_LIST:
			if (fc_isnull[0])
			{
				if (partition_bound_accepts_nulls(fc_boundinfo))
					fc_part_index = fc_boundinfo->null_index;
			}
			else
			{
				bool		fc_equal = false;

				fc_bound_offset = partition_list_bsearch(fc_key->partsupfunc,
													  fc_key->partcollation,
													  fc_boundinfo,
													  fc_values[0], &fc_equal);
				if (fc_bound_offset >= 0 && fc_equal)
					fc_part_index = fc_boundinfo->indexes[fc_bound_offset];
			}
			break;

		case PARTITION_STRATEGY_RANGE:
			{
				bool		fc_equal = false,
							fc_range_partkey_has_null = false;
				int			fc_i;

				/*
				 * 没有范围包括 NULL，因此，如果存在，默认分区将被接受，
				 * 否则将被拒绝。
				 */
				for (fc_i = 0; fc_i < fc_key->partnatts; fc_i++)
				{
					if (fc_isnull[fc_i])
					{
						fc_range_partkey_has_null = true;
						break;
					}
				}

				if (!fc_range_partkey_has_null)
				{
					fc_bound_offset = partition_range_datum_bsearch(fc_key->partsupfunc,
																 fc_key->partcollation,
																 fc_boundinfo,
																 fc_key->partnatts,
																 fc_values,
																 &fc_equal);

					/*
					 * 在 bound_offset 的边界小于或等于元组值，
					 * 因此在 offset+1 的边界是我们寻找的分区的上边界，
					 * 如果真的存在的话。
					 */
					fc_part_index = fc_boundinfo->indexes[fc_bound_offset + 1];
				}
			}
			break;

		default:
			elog(ERROR, "unexpected partition strategy: %d",
				 (int) fc_key->strategy);
	}

	/*
	 * part_index < 0 意味着我们未能找到这个父项的分区。
	 * 如果有，使用默认分区。
	 */
	if (fc_part_index < 0)
		fc_part_index = fc_boundinfo->default_index;

	return fc_part_index;
}

/*
 * ExecBuildSlotPartitionKeyDescription
 *
 * 这与 BuildIndexValueDescription() 非常相似，目前用于在 ExecFindPartition()
 * 未能找到某行的分区时构建错误消息。
 */
static char * fc_ExecBuildSlotPartitionKeyDescription(Relation fc_rel,
									 Datum *fc_values,
									 bool *fc_isnull,
									 int fc_maxfieldlen)
{
	StringInfoData fc_buf;
	PartitionKey fc_key = RelationGetPartitionKey(fc_rel);
	int			fc_partnatts = get_partition_natts(fc_key);
	int			fc_i;
	Oid			fc_relid = RelationGetRelid(fc_rel);
	AclResult	fc_aclresult;

	if (check_enable_rls(fc_relid, InvalidOid, true) == RLS_ENABLED)
		return NULL;

	/* 如果用户具有表级访问权限，则直接构建描述。 */
	fc_aclresult = pg_class_aclcheck(fc_relid, GetUserId(), ACL_SELECT);
	if (fc_aclresult != ACLCHECK_OK)
	{
		/*
		 * 逐步检查分区键的列并确保用户对所有列具有 SELECT 权限。
		 */
		for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
		{
			AttrNumber	fc_attnum = get_partition_col_attnum(fc_key, fc_i);

			/*
			 * 如果这个分区键列是一个表达式，我们不返回任何细节，
			 * 而不是试图弄清楚表达式包含哪个列，以及用户是否对它们具有 SELECT 权限。
			 */
			if (fc_attnum == InvalidAttrNumber ||
				pg_attribute_aclcheck(fc_relid, fc_attnum, GetUserId(),
									  ACL_SELECT) != ACLCHECK_OK)
				return NULL;
		}
	}

	initStringInfo(&fc_buf);
	appendStringInfo(&fc_buf, "(%s) = (",
					 pg_get_partkeydef_columns(fc_relid, true));

	for (fc_i = 0; fc_i < fc_partnatts; fc_i++)
	{
		char	   *fc_val;
		int			fc_vallen;

		if (fc_isnull[fc_i])
			fc_val = "null";
		else
		{
			Oid			fc_foutoid;
			bool		fc_typisvarlena;

			getTypeOutputInfo(get_partition_col_typid(fc_key, fc_i),
							  &fc_foutoid, &fc_typisvarlena);
			fc_val = OidOutputFunctionCall(fc_foutoid, fc_values[fc_i]);
		}

		if (fc_i > 0)
			appendStringInfoString(&fc_buf, ", ");

		/* 根据需要截断 */
		fc_vallen = strlen(fc_val);
		if (fc_vallen <= fc_maxfieldlen)
			appendBinaryStringInfo(&fc_buf, fc_val, fc_vallen);
		else
		{
			fc_vallen = pg_mbcliplen(fc_val, fc_vallen, fc_maxfieldlen);
			appendBinaryStringInfo(&fc_buf, fc_val, fc_vallen);
			appendStringInfoString(&fc_buf, "...");
		}
	}

	appendStringInfoChar(&fc_buf, ')');

	return fc_buf.data;
}

/*
 * adjust_partition_colnos
 *		调整更新目标列编号列表，以考虑父项与分区之间的属性差异。
 *
 * 注意：如果不需要调整，则不得调用。
 */
static List * fc_adjust_partition_colnos(List *fc_colnos, ResultRelInfo *fc_leaf_part_rri)
{
	TupleConversionMap *fc_map = ExecGetChildToRootMap(fc_leaf_part_rri);

	Assert(fc_map != NULL);

	return fc_adjust_partition_colnos_using_map(fc_colnos, fc_map->attrMap);
}

/*
 * adjust_partition_colnos_using_map
 *		像 adjust_partition_colnos，但使用调用者提供的映射，
 *		而不是假设从“根”结果关系进行映射。
 *
 * 注意：如果不需要调整，则不得调用。
 */
static List * fc_adjust_partition_colnos_using_map(List *fc_colnos, AttrMap *fc_attrMap)
{
	List	   *fc_new_colnos = NIL;
	ListCell   *fc_lc;

	Assert(fc_attrMap != NULL);	/* 否则我们不应该在这里 */

	foreach(fc_lc, fc_colnos)
	{
		AttrNumber	fc_parentattrno = lfirst_int(fc_lc);

		if (fc_parentattrno <= 0 ||
			fc_parentattrno > fc_attrMap->maplen ||
			fc_attrMap->attnums[fc_parentattrno - 1] == 0)
			elog(ERROR, "unexpected attno %d in target column list",
				 fc_parentattrno);
		fc_new_colnos = lappend_int(fc_new_colnos,
								 fc_attrMap->attnums[fc_parentattrno - 1]);
	}

	return fc_new_colnos;
}

/*-------------------------------------------------------------------------
 * 运行时分区剪裁支持。
 *
 * 以下一系列函数的存在是为了支持移除不需要的
 * 子计划，以便对分区表进行查询。这里的支持函数
 * 旨在与任何支持任意数量子计划的计划类型配合使用，例如 Append，MergeAppend。
 *
 * 当剪裁涉及将分区键与常量进行比较时，
 * 由规划器完成。然而，如果我们有与非恒定
 * 但不是易失性表达式的比较，那就为执行器提供了
 * 运行时剪裁的机会，允许动态跳过无关的分区。
 *
 * 我们必须区分包含 PARAM_EXEC 参数的表达式和
 * 不包含这些的表达式。即使一个 PARAM_EXEC 参数被
 * 认为是稳定表达式，它的值也可能在查询执行期间
 * 从一个计划节点扫描到下一个计划节点时发生变化。
 * 不涉及这样的参数的稳定比较
 * 表达式允许在执行器启动时进行一次分区剪裁。
 * 涉及这些参数的表达式则要求我们为每次扫描
 * 父计划节点分别进行剪裁。
 *
 * 请注意，在执行器启动期间剪裁掉不需要的子计划
 * 的额外好处是不必初始化那些不需要的子计划。
 *
 *
 * 函数：
 *
 * ExecInitPartitionPruning:
 *		为 ExecFindMatchingSubPlans 创建所需的 PartitionPruneState。
 *		存储的细节包括如何将分区剪裁代码返回的
 *		分区索引映射到子计划索引中。此外，根据执行初始
 *		剪裁步骤（如果有）所考虑的结果，确定要初始化的子计划集。
 *		PartitionPruneState 中的映射会更新以考虑到初始
 *		剪裁可能已消除的一些子计划。
 *
 * ExecFindMatchingSubPlans:
 *		在评估在给定点安全评估的表达式后，返回匹配子计划的索引。
 *		此函数首先在 ExecInitPartitionPruning() 中调用，以基于执行初始
 *		剪裁步骤找到最初匹配的子计划，然后必须在每次
 *		PartitionPruneState 的 'execparamids' 中列出的参数值变化时再次调用。
 *-------------------------------------------------------------------------
 */

/*
 * ExecInitPartitionPruning
 *		初始化运行时分区剪裁所需的数据结构，并在需要时执行初始
 *		剪裁
 *
 * 返回时，*initially_valid_subplans 被分配为必须与父计划节点
 * 一起初始化的子计划索引集。如果需要，此处会执行初始剪裁，
 * 在这种情况下，仅添加幸存子计划的索引。
 *
 * 如果确实剪裁了子计划，所返回的 PartitionPruneState 中的
 * subplan_map 数组将重新排序以不计算这些，尽管只有当在后续
 * 执行剪裁过程中需要这些映射时才会这样做。
 */
PartitionPruneState *
ExecInitPartitionPruning(PlanState *fc_planstate,
						 int fc_n_total_subplans,
						 PartitionPruneInfo *fc_pruneinfo,
						 Bitmapset **fc_initially_valid_subplans)
{
	PartitionPruneState *fc_prunestate;
	EState	   *fc_estate = fc_planstate->state;

	/* 我们可能需要一个表达式上下文来评估分区表达式 */
	ExecAssignExprContext(fc_estate, fc_planstate);

	/* 创建用于剪裁的工作数据结构 */
	fc_prunestate = fc_CreatePartitionPruneState(fc_planstate, fc_pruneinfo);

	/*
	 * 执行初始分区剪裁，如果需要的话。
	 */
	if (fc_prunestate->do_initial_prune)
		*fc_initially_valid_subplans = ExecFindMatchingSubPlans(fc_prunestate, true);
	else
	{
		/* 没有剪裁，因此我们需要初始化所有子计划 */
		Assert(fc_n_total_subplans > 0);
		*fc_initially_valid_subplans = bms_add_range(NULL, 0,
												  fc_n_total_subplans - 1);
	}

	/*
	 * 重新排序 prunestate 中包含的子计划索引，以考虑由于初始剪裁而
	 * 移除的任何索引。如果没有步骤被移除，则不需要这样做。
	 */
	if (bms_num_members(*fc_initially_valid_subplans) < fc_n_total_subplans)
	{
		/*
		 * 当 !do_exec_prune 时我们可以安全地跳过这一步，尽管这会
		 * 在 prunestate 中留下无效数据，因为这些数据将不会再被
		 * 再次参考（参见 ExecFindMatchingSubPlans 中的初始断言）。
		 */
		if (fc_prunestate->do_exec_prune)
			fc_PartitionPruneFixSubPlanMap(fc_prunestate,
										*fc_initially_valid_subplans,
										fc_n_total_subplans);
	}

	return fc_prunestate;
}

/*
 * CreatePartitionPruneState
 *		构建调用 ExecFindMatchingSubPlans 所需的数据结构
 *
 * 'planstate' 是父计划节点的执行状态。
 *
 * 'pruneinfo' 是由 make_partition_pruneinfo 生成的 PartitionPruneInfo。
 * 在这里，我们构建一个 PartitionPruneState，其中包含每个分区层次
 * 的 PartitioningData（即，pruneinfo->prune_infos 的每个子列表），
 * 每个子列表中包含每个 PartitionedRelPruneInfo 的 PartitionedRelPruningData。
 * 这种两级系统是为了防止在 UNION ALL 包含多个分区表作为子计划时
 * 混淆不同的层次。在每个 PartitionedRelPruningData 中存储的数据
 * 可以在我们每次重新评估哪些分区与每个 PartitionedRelPruneInfo 中提供的
 * 剪裁步骤匹配时重复使用。
 */
static PartitionPruneState *
fc_CreatePartitionPruneState(PlanState *fc_planstate, PartitionPruneInfo *fc_pruneinfo)
{
	EState	   *fc_estate = fc_planstate->state;
	PartitionPruneState *fc_prunestate;
	int			fc_n_part_hierarchies;
	ListCell   *fc_lc;
	int			fc_i;
	ExprContext *fc_econtext = fc_planstate->ps_ExprContext;

	/* 对于数据读取，执行器始终包括分离的分区 */
	if (fc_estate->es_partition_directory == NULL)
		fc_estate->es_partition_directory =
			CreatePartitionDirectory(fc_estate->es_query_cxt, false);

	fc_n_part_hierarchies = list_length(fc_pruneinfo->prune_infos);
	Assert(fc_n_part_hierarchies > 0);

	/*
	 * 分配数据结构
	 */
	fc_prunestate = (PartitionPruneState *)
		palloc(offsetof(PartitionPruneState, partprunedata) +
			   sizeof(PartitionPruningData *) * fc_n_part_hierarchies);

	fc_prunestate->execparamids = NULL;
	
/* other_subplans 可以在运行时更改，因此我们需要自己的副本 */
	fc_prunestate->other_subplans = bms_copy(fc_pruneinfo->other_subplans);
	fc_prunestate->do_initial_prune = false;	/* 可能在下面设置 */
	fc_prunestate->do_exec_prune = false;	/* 可能在下面设置 */
	fc_prunestate->num_partprunedata = fc_n_part_hierarchies;

	/*
	 * 创建一个短期内存上下文，我们将在调用分区修剪函数时使用它。
	 * 这避免了可能的内存泄漏，因为修剪函数调用的比较函数不在
	 * 我们的控制之下。
	 */
	fc_prunestate->prune_context =
		AllocSetContextCreate(CurrentMemoryContext,
							  "Partition Prune",
							  ALLOCSET_DEFAULT_SIZES);

	fc_i = 0;
	foreach(fc_lc, fc_pruneinfo->prune_infos)
	{
		List	   *fc_partrelpruneinfos = lfirst_node(List, fc_lc);
		int			fc_npartrelpruneinfos = list_length(fc_partrelpruneinfos);
		PartitionPruningData *fc_prunedata;
		ListCell   *fc_lc2;
		int			fc_j;

		fc_prunedata = (PartitionPruningData *)
			palloc(offsetof(PartitionPruningData, partrelprunedata) +
				   fc_npartrelpruneinfos * sizeof(PartitionedRelPruningData));
		fc_prunestate->partprunedata[fc_i] = fc_prunedata;
		fc_prunedata->num_partrelprunedata = fc_npartrelpruneinfos;

		fc_j = 0;
		foreach(fc_lc2, fc_partrelpruneinfos)
		{
			PartitionedRelPruneInfo *fc_pinfo = lfirst_node(PartitionedRelPruneInfo, fc_lc2);
			PartitionedRelPruningData *fc_pprune = &fc_prunedata->partrelprunedata[fc_j];
			Relation	fc_partrel;
			PartitionDesc fc_partdesc;
			PartitionKey fc_partkey;

			/*
			 * 我们可以依赖于分区表的分区键和分区描述符的副本出现在其 relcache 条目中，
			 * 因为该条目将在此执行器运行期间保持打开和锁定。
			 */
			fc_partrel = ExecGetRangeTableRelation(fc_estate, fc_pinfo->rtindex);
			fc_partkey = RelationGetPartitionKey(fc_partrel);
			fc_partdesc = PartitionDirectoryLookup(fc_estate->es_partition_directory,
												fc_partrel);

			/*
			 * 初始化 subplan_map 和 subpart_map。
			 *
			 * 现在存在的分区集合可能与制定计划时存在的分区集合不同。正常的情况是相同的；优化这个情况，通过快速比较，并直接复制 subplan_map，并使 subpart_map 指向 PruneInfo 中的那一个。
			 *
			 * 对于它们不相同的情况，我们可能在任一侧有更多的分区；或者甚至在两侧有完全相同的数量，但 OID 集合并不完全匹配。通过创建新的 subplan_map 和 subpart_map 数组来处理这个问题，这些数组与 PruneInfo 中的新分区描述符的 OID 匹配。任何不匹配的可以设置为 -1，仿佛它们被修剪了。根据构造，这两个数组都按分区边界顺序排列。
			 */
			fc_pprune->nparts = fc_partdesc->nparts;
			fc_pprune->subplan_map = palloc(sizeof(int) * fc_partdesc->nparts);

			if (fc_partdesc->nparts == fc_pinfo->nparts &&
				memcmp(fc_partdesc->oids, fc_pinfo->relid_map,
					   sizeof(int) * fc_partdesc->nparts) == 0)
			{
				fc_pprune->subpart_map = fc_pinfo->subpart_map;
				memcpy(fc_pprune->subplan_map, fc_pinfo->subplan_map,
					   sizeof(int) * fc_pinfo->nparts);
			}
			else
			{
				int			fc_pd_idx = 0;
				int			fc_pp_idx;

				/*
				 * 当分区数组不相同时，可能有一些新的分区，但也可能有一个分区被移除；我们通过遍历数组并丢弃那些不匹配的来应对这两种情况。
				 *
				 * 如果两侧的分区数量相匹配，但仍然可能有一个分区被分离而另一个分区被附加。通过创建一个跳过任何不匹配的映射来处理这种情况。
				 */
				fc_pprune->subpart_map = palloc(sizeof(int) * fc_partdesc->nparts);

				for (fc_pp_idx = 0; fc_pp_idx < fc_partdesc->nparts; fc_pp_idx++)
				{
					/* 跳过任何无效的 relid_map 条目 */
					while (fc_pd_idx < fc_pinfo->nparts &&
						   !OidIsValid(fc_pinfo->relid_map[fc_pd_idx]))
						fc_pd_idx++;

			recheck:
					if (fc_pd_idx < fc_pinfo->nparts &&
						fc_pinfo->relid_map[fc_pd_idx] == fc_partdesc->oids[fc_pp_idx])
					{
						/* match... */
						fc_pprune->subplan_map[fc_pp_idx] =
							fc_pinfo->subplan_map[fc_pd_idx];
						fc_pprune->subpart_map[fc_pp_idx] =
							fc_pinfo->subpart_map[fc_pd_idx];
						fc_pd_idx++;
						continue;
					}

					/*
					 * 在两个数组的对应位置没有精确匹配。查看 pinfo->relid_map，以了解当前分区在 partdesc 是否有匹配。通常，如果存在匹配，则仅仅是前面的一个元素，这意味着规划者看到了一个额外的分区，而我们现在看不到（它的并发分离在两个之间完成）；所以我们通过将 pd_idx 更新到新位置并跳过它来跳过该分区。然后我们可以继续匹配其余的元素，跳过没有匹配的 OID；对于被跳过的元素，不再尝试未来的匹配，因为我们知道数组是按相同的顺序排列的。
					 *
					 * 如果我们在 pinfo->relid_map 数组的其余部分没有看到匹配，意味着我们现在看到一个规划者没有看到的元素，所以将其标记为已修剪并继续处理。
					 */
					for (int fc_pd_idx2 = fc_pd_idx + 1; fc_pd_idx2 < fc_pinfo->nparts; fc_pd_idx2++)
					{
				#ifndef FDD //cppcheck（警告：对立内条件）CWE-398 对立内部“if”条件导致代码块无效。
						if (fc_pd_idx2 >= fc_pinfo->nparts)
							break;
				#endif
						if (fc_pinfo->relid_map[fc_pd_idx2] == fc_partdesc->oids[fc_pp_idx])
						{
							fc_pd_idx = fc_pd_idx2;
							goto recheck;
						}
					}

					fc_pprune->subpart_map[fc_pp_idx] = -1;
					fc_pprune->subplan_map[fc_pp_idx] = -1;
				}
			}

			/* present_parts 也会受到后续修改的影响 */
			fc_pprune->present_parts = bms_copy(fc_pinfo->present_parts);

			/*
			 * 根据需要初始化修剪上下文。
			 */
			fc_pprune->initial_pruning_steps = fc_pinfo->initial_pruning_steps;
			if (fc_pinfo->initial_pruning_steps)
			{
				fc_InitPartitionPruneContext(&fc_pprune->initial_context,
										  fc_pinfo->initial_pruning_steps,
										  fc_partdesc, fc_partkey, fc_planstate,
										  fc_econtext);
				/* 记录是否在任何级别上需要初始修剪 */
				fc_prunestate->do_initial_prune = true;
			}
			fc_pprune->exec_pruning_steps = fc_pinfo->exec_pruning_steps;
			if (fc_pinfo->exec_pruning_steps)
			{
				fc_InitPartitionPruneContext(&fc_pprune->exec_context,
										  fc_pinfo->exec_pruning_steps,
										  fc_partdesc, fc_partkey, fc_planstate,
										  fc_econtext);
				/* 记录是否在任何级别上需要执行修剪 */
				fc_prunestate->do_exec_prune = true;
			}

			/*
			 * 累积影响此计划节点的分区决策的所有 PARAM_EXEC 参数的 ID。
			 */
			fc_prunestate->execparamids = bms_add_members(fc_prunestate->execparamids,
													   fc_pinfo->execparamids);

			fc_j++;
		}
		fc_i++;
	}

	return fc_prunestate;
}

/*
 * 为给定的修剪步骤列表初始化 PartitionPruneContext。
 */
static void fc_InitPartitionPruneContext(PartitionPruneContext *fc_context,
						  List *fc_pruning_steps,
						  PartitionDesc fc_partdesc,
						  PartitionKey fc_partkey,
						  PlanState *fc_planstate,
						  ExprContext *fc_econtext)
{
	int			fc_n_steps;
	int			fc_partnatts;
	ListCell   *fc_lc;

	fc_n_steps = list_length(fc_pruning_steps);

	fc_context->strategy = fc_partkey->strategy;
	fc_context->partnatts = fc_partnatts = fc_partkey->partnatts;
	fc_context->nparts = fc_partdesc->nparts;
	fc_context->boundinfo = fc_partdesc->boundinfo;
	fc_context->partcollation = fc_partkey->partcollation;
	fc_context->partsupfunc = fc_partkey->partsupfunc;

	/* 我们将在需要时查找特定类型的支持函数 */
	fc_context->stepcmpfuncs = (FmgrInfo *)
		palloc0(sizeof(FmgrInfo) * fc_n_steps * fc_partnatts);

	fc_context->ppccontext = CurrentMemoryContext;
	fc_context->planstate = fc_planstate;
	fc_context->exprcontext = fc_econtext;

	/* 为我们需要的每个表达式初始化表达式状态 */
	fc_context->exprstates = (ExprState **)
		palloc0(sizeof(ExprState *) * fc_n_steps * fc_partnatts);
	foreach(fc_lc, fc_pruning_steps)
	{
		PartitionPruneStepOp *fc_step = (PartitionPruneStepOp *) lfirst(fc_lc);
		ListCell   *fc_lc2 = list_head(fc_step->exprs);
		int			fc_keyno;

		/* 不需要其他步骤类型 */
		if (!IsA(fc_step, PartitionPruneStepOp))
			continue;

		Assert(list_length(fc_step->exprs) <= fc_partnatts);

		for (fc_keyno = 0; fc_keyno < fc_partnatts; fc_keyno++)
		{
			if (bms_is_member(fc_keyno, fc_step->nullkeys))
				continue;

			if (fc_lc2 != NULL)
			{
				Expr	   *fc_expr = lfirst(fc_lc2);

				/* 对于常量不需要 */
				if (!IsA(fc_expr, Const))
				{
					int			fc_stateidx = PruneCxtStateIdx(fc_partnatts,
															fc_step->step.step_id,
															fc_keyno);

					/*
					 * 当 planstate 为 NULL 时，pruning_steps 知道不包含任何依赖于父计划的表达式。任何可用的 EXTERN 参数的信息必须在这种情况下显式传递，调用者必须通过 econtext 提供这些信息。
					 */
					if (fc_planstate == NULL)
						fc_context->exprstates[fc_stateidx] =
							ExecInitExprWithParams(fc_expr,
												   fc_econtext->ecxt_param_list_info);
					else
						fc_context->exprstates[fc_stateidx] =
							ExecInitExpr(fc_expr, fc_context->planstate);
				}
				fc_lc2 = lnext(fc_step->exprs, fc_lc2);
			}
		}
	}
}

/*
 * PartitionPruneFixSubPlanMap
 *		通过考虑在初始修剪中幸存下来的新 subplans 列表，修正 prunestate 中分区索引与 subplan 索引的映射
 *
 * PartitionPruneState 中当前存在的索引值计算了在初始修剪之前所有应存在的 subplans。如果初始修剪去掉了一些 subplans，任何后续的修剪将查看与初始修剪集中的 subplans 不同的目标 subplans 集，因此 PartitionPruneState 中包含这些索引的映射必须更新以反映后初始修剪集中 subplans 的新索引。
 */
static void fc_PartitionPruneFixSubPlanMap(PartitionPruneState *fc_prunestate,
							Bitmapset *fc_initially_valid_subplans,
							int fc_n_total_subplans)
{
	int		   *fc_new_subplan_indexes;
	Bitmapset  *fc_new_other_subplans;
	int			fc_i;
	int			fc_newidx;

	/*
	 * 首先，我们必须构建一个临时数组，将旧的 subplan 索引映射到新的索引。为了方便初始化，我们在这个数组中使用 1 基索引，并将修剪的项目留作 0。
	 */
	fc_new_subplan_indexes = (int *) palloc0(sizeof(int) * fc_n_total_subplans);
	fc_newidx = 1;
	fc_i = -1;
	while ((fc_i = bms_next_member(fc_initially_valid_subplans, fc_i)) >= 0)
	{
		Assert(fc_i < fc_n_total_subplans);
		fc_new_subplan_indexes[fc_i] = fc_newidx++;
	}

	/*
	 * 现在我们可以使用新的 subplan 索引更新每个 PartitionedRelPruneInfo 的 subplan_map。我们还必须重新计算其 present_parts 位图。
	 */
	for (fc_i = 0; fc_i < fc_prunestate->num_partprunedata; fc_i++)
	{
		PartitionPruningData *fc_prunedata = fc_prunestate->partprunedata[fc_i];
		int			fc_j;

		/*
		 * 在每个层次内，我们以反向顺序执行此循环，以便首先确定最低级分区表的 present_parts。通过这种方式，我们可以判断一个子分区表的分区是否被完全修剪，从而可以将其排除在当前级别的 present_parts 之外。
		 */
		for (fc_j = fc_prunedata->num_partrelprunedata - 1; fc_j >= 0; fc_j--)
		{
			PartitionedRelPruningData *fc_pprune = &fc_prunedata->partrelprunedata[fc_j];
			int			fc_nparts = fc_pprune->nparts;
			int			fc_k;

			/* 我们只是从头开始重建 present_parts */
			bms_free(fc_pprune->present_parts);
			fc_pprune->present_parts = NULL;

			for (fc_k = 0; fc_k < fc_nparts; fc_k++)
			{
				int			fc_oldidx = fc_pprune->subplan_map[fc_k];
				int			fc_subidx;

				
/* 
 * 如果这个分区存在作为一个子计划，那么将旧的子计划索引更改为新的子计划索引。新的索引可能会变为 -1 如果上面进行了分区修剪，或者由于一些子计划在列表中更早地被移除，它可能会更早地出现在子计划列表中。如果它是一个子分区，除非完全被修剪，否则将其添加到 present_parts。
 */
				if (fc_oldidx >= 0)
				{
					Assert(fc_oldidx < fc_n_total_subplans);
					fc_pprune->subplan_map[fc_k] = fc_new_subplan_indexes[fc_oldidx] - 1;

					if (fc_new_subplan_indexes[fc_oldidx] > 0)
						fc_pprune->present_parts =
							bms_add_member(fc_pprune->present_parts, fc_k);
				}
				else if ((fc_subidx = fc_pprune->subpart_map[fc_k]) >= 0)
				{
					PartitionedRelPruningData *fc_subprune;

					fc_subprune = &fc_prunedata->partrelprunedata[fc_subidx];

					if (!bms_is_empty(fc_subprune->present_parts))
						fc_pprune->present_parts =
							bms_add_member(fc_pprune->present_parts, fc_k);
				}
			}
		}
	}

	 /*
 * 我们还必须重新计算其他子计划集，因为其中的索引可能会改变。
 */
	fc_new_other_subplans = NULL;
	fc_i = -1;
	while ((fc_i = bms_next_member(fc_prunestate->other_subplans, fc_i)) >= 0)
		fc_new_other_subplans = bms_add_member(fc_new_other_subplans,
											fc_new_subplan_indexes[fc_i] - 1);

	bms_free(fc_prunestate->other_subplans);
	fc_prunestate->other_subplans = fc_new_other_subplans;

	pfree(fc_new_subplan_indexes);
}

 /*
 * ExecFindMatchingSubPlans
 * 确定哪些子计划与“prunestate”中详细说明的修剪步骤匹配，用于当前的比较表达式值。
 *
 * 如果 PARAM_EXEC Params 尚无法评估，则传递 initial_prune。这使得初始执行器时间修剪步骤与后来的运行时修剪区分开来。
 */
Bitmapset * ExecFindMatchingSubPlans(PartitionPruneState *fc_prunestate,
						 bool fc_initial_prune)
{
	Bitmapset  *fc_result = NULL;
	MemoryContext fc_oldcontext;
	int			fc_i;

	 /*
 * 我们要么在修剪初始化期间执行的初始修剪中，要么在可以评估 PARAM_EXEC Params 的时刻，并且有步骤可以执行评估。
 */
	Assert(fc_initial_prune || fc_prunestate->do_exec_prune);

	 /*
 * 切换到临时上下文，以避免在执行器的查询生命周期内存上下文中泄漏内存。
 */
	fc_oldcontext = MemoryContextSwitchTo(fc_prunestate->prune_context);

	 /*
 * 对于每个层次结构，进行修剪测试，并将不可删除的子计划索引添加到“result”。
 */
	for (fc_i = 0; fc_i < fc_prunestate->num_partprunedata; fc_i++)
	{
		PartitionPruningData *fc_prunedata = fc_prunestate->partprunedata[fc_i];
		PartitionedRelPruningData *fc_pprune;

		 /*
 * 我们传递属于层次结构根表的第一个项目，find_matching_subplans_recurse() 会根据需要递归到其他（较低级别）父项。
 */
		fc_pprune = &fc_prunedata->partrelprunedata[0];
		fc_find_matching_subplans_recurse(fc_prunedata, fc_pprune, fc_initial_prune,
									   &fc_result);

		 /* 表达式评估可能在 ExprContext 中使用了空间 */
		if (fc_pprune->exec_pruning_steps)
			ResetExprContext(fc_pprune->exec_context.exprcontext);
	}

	 /* 添加任何未被分区修剪考虑到的子计划 */
	fc_result = bms_add_members(fc_result, fc_prunestate->other_subplans);

	MemoryContextSwitchTo(fc_oldcontext);

	 /* 在重置临时上下文之前将结果复制出 */
	fc_result = bms_copy(fc_result);

	MemoryContextReset(fc_prunestate->prune_context);

	return fc_result;
}

/*
 * find_matching_subplans_recurse
 *		递归工作函数，用于 ExecFindMatchingSubPlans
 *
 * 将有效的（不可修剪的）子计划 ID 添加到 *validsubplans
 */
static void fc_find_matching_subplans_recurse(PartitionPruningData *fc_prunedata,
							   PartitionedRelPruningData *fc_pprune,
							   bool fc_initial_prune,
							   Bitmapset **fc_validsubplans)
{
	Bitmapset  *fc_partset;
	int			fc_i;

	/* 防止由于过深的分区层次结构导致堆栈溢出。 */
	check_stack_depth();

	/*
	 * 如有必要，进行修剪，如果我们有与当前执行上下文匹配的修剪步骤。
	 * 否则只需包含此级别的所有分区。
	 */
	if (fc_initial_prune && fc_pprune->initial_pruning_steps)
		fc_partset = get_matching_partitions(&fc_pprune->initial_context,
										  fc_pprune->initial_pruning_steps);
	else if (!fc_initial_prune && fc_pprune->exec_pruning_steps)
		fc_partset = get_matching_partitions(&fc_pprune->exec_context,
										  fc_pprune->exec_pruning_steps);
	else
		fc_partset = fc_pprune->present_parts;

	/* 将 partset 转换为子计划索引 */
	fc_i = -1;
	while ((fc_i = bms_next_member(fc_partset, fc_i)) >= 0)
	{
		if (fc_pprune->subplan_map[fc_i] >= 0)
			*fc_validsubplans = bms_add_member(*fc_validsubplans,
											fc_pprune->subplan_map[fc_i]);
		else
		{
			int			fc_partidx = fc_pprune->subpart_map[fc_i];

			if (fc_partidx >= 0)
				fc_find_matching_subplans_recurse(fc_prunedata,
											   &fc_prunedata->partrelprunedata[fc_partidx],
											   fc_initial_prune, fc_validsubplans);
			else
			{
				/*
				 * 如果规划器已修剪掉此分区的所有子分区，我们将在此处。
				 * 在这种情况下，默默忽略此分区。最终结果是相同的：
				 * 我们本来会修剪掉所有分区，但我们
				 * 没有任何修剪步骤可以执行以确认这一点。
				 */
			}
		}
	}
}
